1. 禁止多端用户登陆
在Spring Security中,禁止多端用户登陆有两种方式:
- 后来的登陆踢掉已经登陆的用户
- 已经登陆的用户,后来的登陆不被允许
1.1 踢掉已经登陆的用户
这两种方式在Spring Security中都很好实现,只需要配置一下sessionManager:
1 |
|
当完成上面的配置后,后来的登陆会踢掉已经登陆的用户,当已经登陆的用户再次访问接口时,会报错如下:
1 | This session has been expired (possibly due to multiple concurrent logins being attempted as the same user). |
可以看到,这里说这个 session 已经过期,原因则是由于使用同一个用户进行并发登录。
1.2 后来的登陆不被允许
另外一种方式,后来的登陆不被允许的设置也非常简单,添加一个配置:
1 |
|
由于Spring Security是监听session来清理session的记录的。而用户从不同的浏览器登陆后,都会有不同的session,当用户注销登陆之后,session就会失效,但是默认的失效是调用StandardSession#invalidate方法来清理session的。这一个失效事件无法被Spring容器感知到,进而导致用户注销登陆后,session信息没有及时清理,进而导致用户无法重新登陆进来。所以,我们需要一个事件发布器来监听session的事件:
1 |
|
当完成以上配置后,如果用户已经登陆过,后来的登陆不被允许,会报错。
2. 源码分析
首先我们知道,在用户登录的过程中,会经过 UsernamePasswordAuthenticationFilter,而 UsernamePasswordAuthenticationFilter 中过滤方法的调用是在 AbstractAuthenticationProcessingFilter 中触发的,我们来看下 AbstractAuthenticationProcessingFilter#doFilter 方法的调用:
1 | private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) |
在这段代码中,我们可以看到,调用 attemptAuthentication 方法走完认证流程之后,回来之后,接下来就是调用 sessionStrategy.onAuthentication 方法,这个方法就是用来处理 session 的并发问题的。具体在ConcurrentSessionControlAuthenticationStrategy:
1 |
|
allowableSessionsExceeded 方法中,首先会有 exceptionIfMaximumExceeded 属性,这就是我们在 SecurityConfig 中配置的 maxSessionsPreventsLogin 的值,默认为 false,如果为 true,就直接抛出异常,那么这次登录就失败了(对应 1.1 小节踢掉已经登陆用户的效果),如果为 false,则对 sessions 按照请求时间进行排序,然后再使多余的 session 过期即可(对应 1.2 小节后来的登陆不被允许的效果)。
1 | protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, |